package com.sky.server;

import com.alibaba.nacos.shaded.com.google.gson.Gson;
import com.sky.constant.NettyConstant;
import com.sky.entity.MessageRequest;
import com.sky.util.NettyUtils;
import com.sky.utils.RedisHashUtils;
import com.sky.utils.StringUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.util.Map;

/**
 * 自定义的Handler需要继承Netty规定好的HandlerAdapter
 * 才能被Netty框架所关联，有点类似SpringMVC的适配器模式
 *
 * @author Administrator
 **/
@Component
@ChannelHandler.Sharable
public class WebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Value("${websocket.port}")
    private Integer websocketPort;

    @Resource
    private PublishService publishService;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 与netty服务器推送消息时触发
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        // 封装请求信息
        MessageRequest messageRequest = MessageRequest.build(msg.text());
        String channelId = ctx.channel().id().asLongText();
        // 获取当前登录用户的信息
        Long userId = messageRequest.getUserId();
        // 保存用户与channel的映射关系
        RedisHashUtils.hashSet(NettyConstant.USER_CHANNEL_CACHE,userId.toString(),channelId);
        String serverUrl = InetAddress.getLocalHost().getHostAddress() + ":" + websocketPort;
        // 保存用户与netty 服务器的映射关系
        RedisHashUtils.hashSet(NettyConstant.USER_SERVER_CACHE,userId.toString(),serverUrl);

        if (StringUtils.isNotEmpty(messageRequest.getMessage()) && StringUtils.isNotEmpty(messageRequest.getReceiverUserId())){
            // 消息发送
            Long receiverUserId = messageRequest.getReceiverUserId();
            // 查询接受消息的用户channel连接在那台服务器上（然后服务器进行转发）
            String sendServerUrl = (String) RedisHashUtils.hashGet(NettyConstant.USER_SERVER_CACHE, receiverUserId.toString());
            if (!serverUrl.equals(sendServerUrl)){
                // 使用redis发布订阅消息
                String message = new Gson().toJson(messageRequest);
                publishService.sendMsg(message);
            } else {
                // 1、获取当前用户所的channel
                String clientChannelId = (String) RedisHashUtils.hashGet(NettyConstant.USER_CHANNEL_CACHE, receiverUserId.toString());
                // 2、通过channelId获取本服务器与其的连接channel
                Channel channel = NettyUtils.nettyChannelMap.get(clientChannelId);
                // 3、消息转发
                channel.writeAndFlush(new TextWebSocketFrame(messageRequest.getMessage()));
            }
        }
    }


    /**
     * 与netty服务器建立连接时触发
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        String channelId = ctx.channel().id().asLongText();
        // 将客户端的channel放入map中
        NettyUtils.nettyChannelMap.put(channelId,ctx.channel());
        super.handlerAdded(ctx);
    }

    /**
     * 与netty服务器断开连接时触发
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 客户端下线，将客户端踢出在线集合
        String channelId = ctx.channel().id().asLongText();
        // 用户下线
        deadLine(channelId);
    }

    /**
     * 与netty服务器断开发生异常时触发
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        String channelId = ctx.channel().id().asLongText();
        // 用户下线
        deadLine(channelId);
    }

    /**
     * 用户下线时，清除缓存
     */
    private void deadLine(String channelId) {
        // 清除通道缓存
        NettyUtils.nettyChannelMap.remove(channelId);
        Map entries = redisTemplate.opsForHash().entries("user_channel_cache:");
        if (StringUtils.isNotEmpty(entries)){
            entries.forEach((k,v)->{
                if (v.equals(channelId)){
                    // 获取用户id
                    String userId = k.toString();
                    String id = v.toString();
                    if (channelId.equals(id)){
                        // 清除用户与netty服务器的缓存
                        RedisHashUtils.hashDel(NettyConstant.USER_SERVER_CACHE,userId);
                        RedisHashUtils.hashDel(NettyConstant.USER_CHANNEL_CACHE,userId);
                    }
                }
            });
        }
    }
}
